/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.impl;

import picounit.Invokable;
import picounit.features.mocking.MethodUtil;
import picounit.features.signature_factory.Signature;
import picounit.features.signature_factory.SignatureFactory;
import picounit.features.test_instantiator.Invoker;
import previous.picounit.Should;
import previous.picounit.Test;
import previous.picounit.Verify;
import previous.picounit.mocker.MockFactory;

import java.lang.reflect.Method;

public class InvokerTest implements Test {
	private Invoker invoker;
	
	private Invokable invokable;
	private SignatureFactory signatureFactory;
	
	public static class Fixture {}

	public interface ToInvoke {
		void methodWithZeroArguments();
		void methodWithSomeArguments(Fixture fixture);
		
		Invokable methodReturningInvokable();

		public static final Method methodWithZeroArguments =
			new MethodUtil().getMethod(ToInvoke.class, "methodWithZeroArguments");

		public static final Method methodWithSomeArguments =
			new MethodUtil().getMethod(ToInvoke.class, "methodWithSomeArguments", Fixture.class);
		
		public static final Method methodReturningInvokable =
			new MethodUtil().getMethod(ToInvoke.class, "methodReturningInvokable");
	}

	private ToInvoke toInvoke;
	private Signature signature;

	private MockFactory mockFactory;
	
	public InvokerTest(MockFactory factory) {
		this.mockFactory = factory;
	}
	
	public void mock(Invokable invokable, ToInvoke toInvoke, SignatureFactory signatureFactory,
		Signature signature) {
		
		this.signatureFactory = signatureFactory;
		this.signature = signature;
		this.invoker = new Invoker(signatureFactory);
		
		this.invokable = invokable;
		this.toInvoke = toInvoke;
	}
	
	public void testInvokeMethodWithZeroArguments(Should should) throws Exception {
		should.call(signatureFactory.create(ToInvoke.methodWithZeroArguments))
			.andReturn(signature);

		should.call(signature.parameterValues())
			.andReturn((new Object[0]));

		toInvoke.methodWithZeroArguments();

		should.expectAboveWhenTheFollowingOccurs();

		invoker.invoke(ToInvoke.methodWithZeroArguments, toInvoke);
	}
	
	public void testInvokeMethodWithSomeArguments(Should should) throws Exception {
		Fixture fixture = new Fixture();

		toInvoke.methodWithSomeArguments(fixture);

		should.call(signatureFactory.create(ToInvoke.methodWithSomeArguments))
			.andReturn(signature);

		should.call(signature.parameterValues())
			.andReturn((new Object[] {fixture}));

		should.expectAboveWhenTheFollowingOccurs();

		invoker.invoke(ToInvoke.methodWithSomeArguments, toInvoke);
	}
	
	public void testInvokingMethodThatReturnsInvokableInvokesTheInvokable(Should should) throws Exception {
		should.call(signatureFactory.create(ToInvoke.methodReturningInvokable))
			.andReturn(signature);

		should.call(signature.parameterValues())
			.andReturn((new Object[0]));

		should.call(toInvoke.methodReturningInvokable()).andReturn(invokable);
		
		invokable.invoke();

		should.expectAboveWhenTheFollowingOccurs();

		invoker.invoke(ToInvoke.methodReturningInvokable, toInvoke);
	}

	public static class SomeClass {
		public static final Method picoSetUp = new MethodUtil().getMethod(SomeClass.class, "setUp", Fixture.class);
		public static final Method junitSetUp = new MethodUtil().getMethod(SomeClass.class, "setUp");

		// PicoUnit style setUp
		public void setUp(Fixture fixture) {
		}

		// JUnit style setUp
		protected void setUp() throws Exception {
		}
	}

	public void testInvokesProtectedMethods(Should should) throws Exception {
		Fixture fixture = new Fixture();
		
		Signature junitSetupSignature = mockFactory.mock(Signature.class);
		Signature picoSetupSignature = mockFactory.mock(Signature.class);

		should.call(signatureFactory.create(SomeClass.junitSetUp))
			.andReturn(junitSetupSignature);
		
		should.call(junitSetupSignature.parameterValues())
			.andReturn((new Object[0]));
		
		should.call(signatureFactory.create(SomeClass.picoSetUp))
			.andReturn(picoSetupSignature);
		
		should.call(picoSetupSignature.parameterValues())
			.andReturn((new Object[] {fixture}));

		should.expectAboveWhenTheFollowingOccurs();

		SomeClass someClass = new SomeClass();

		invoker.invoke("setUp", someClass);
	}
	
	public static class Base {
		private final StringBuffer stringBuffer;

		public Base(StringBuffer stringBuffer) {
			this.stringBuffer = stringBuffer;
		}
		
		public void method() {
			stringBuffer.append("Base.method ");
		}
		
		public static final Method method = new MethodUtil().getMethod(Base.class, "method");
	}
	
	public static class Derived extends Base {
		private final StringBuffer stringBuffer;

		public Derived(StringBuffer stringBuffer) {
			super(stringBuffer);
			this.stringBuffer = stringBuffer;
		}
		
		public void method(String parameter) {
			stringBuffer.append("Derived.method(" + parameter + ") ");			
		}
		
		public static final Method method = new MethodUtil().getMethod(Derived.class, "method", String.class);
	}
	
	public void testInvokesBaseMethodsBeforeDerivedMethods(Should should, Verify verify) throws Exception {
		Signature baseMethodSignature = mockFactory.mock(Signature.class);
		Signature derivedMethodSignature = mockFactory.mock(Signature.class);

		should.call(signatureFactory.create(Base.method))
			.andReturn(baseMethodSignature);
		
		should.call(baseMethodSignature.parameterValues())
			.andReturn(new Object[0]);

		should.call(signatureFactory.create(Derived.method))
			.andReturn(derivedMethodSignature);

		should.call(derivedMethodSignature.parameterValues())
			.andReturn(new Object[] {"parameter"});

		should.expectAboveWhenTheFollowingOccurs();

		StringBuffer stringBuffer = new StringBuffer();

		invoker.invoke("method", new Derived(stringBuffer));

		verify.that(stringBuffer.toString().trim())
			.isEqualTo("Base.method Derived.method(parameter)");
	}
}
